我們來看一下,React 最一開始教大家用 props 寫共用 component 的方法
// with props
const product: Product = {
id: 1,
name: 'product',
title: 'product',
price: 99,
category: 'product',
rating: 4,
image: 'https://picsum.photos/seed/1/640/480',
discount: 0.2,
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quibusdam.'
}
<ProductCard product = {product} />
// ProductCard
function ProductCard({ product }: Props) {
return (
<div className={styles.productCard}>
<div className={styles.imageContainer} >
<img className={styles.image} src={product.image} alt={product.title} />
</div>
<div className={styles.info}>{product.name}</div>
<div className={styles.description}>{product.description}</div>
<div className={styles.buttonContainer}>
<div className={styles.button}> add to cart </div>
</div>
</div>
);
}
上述這樣做法會有一些小問題要處理
function ProductCard({ product , showButton }: Props) {
return (
<div className={styles.productCard}>
<div className={styles.imageContainer} >
<img className={styles.image} src={product.image} alt={product.title} />
</div>
<div className={styles.info}>{product.name}</div>
<div className={styles.description}>{product.description}</div>
{
showButton &&
<div className={styles.buttonContainer}>
<div className={styles.button}> add to cart </div>
</div>
}
</div>
);
}
當然也可以將每個部分再切成元件之後再放入 ProductCard 裡面,components 整理過後可能會是長這樣
function ProductCard({ product , showButton }: Props) {
return (
<div className={styles.productCard}>
<ProdcutImage image = {product.image}/>
<ProductName info = {product.info}/>
<ProdcutDescription description = {product.description}/>
{showButton && <Button />}
</div>
);
}
為了解決剛剛列出的問題,也讓我們的 component 更好維護,就可以使用 Compound Component。
要使用 compound component 我們會用到 useContext,透過 useContext 把本來用 props 的方式改成利用provider 給資料
//ProductCardContext
import * as React from 'react';
import { createContext, useContext } from 'react';
import { Product } from '../types';
const ProductCardContext = createContext<{ product: Product } | null>(null);
export function useProductCardContext() {
const context = useContext(ProductCardContext);
if (!context) {
throw new Error(
'ProductCard.* component must be rendered as child of ProductCard component'
);
}
return context;
}
export default ProductCardContext;
import { ReactNode } from 'react';
import * as React from 'react';
import ProductCardContext from './ProductCardContext';
import { Product } from '../types';
import ProductImage from './ProductImage';
import ProductButton from './ProductButton';
import ProductTitle from './ProductTitle';
import ProductInfo from './ProductInfo';
import ProductCategory from './ProductCategory';
import ProductRating from './ProductRating';
import ProductPrice from './ProductPrice';
type Props = {
product: Product;
image?: ReactNode;
info?: ReactNode;
action?: ReactNode;
};
function ProductCard({ image, info, action, product }: Props) {
return (
<ProductCardContext.Provider value={{ product }}>
<div className="product-card">
{image}
<div className="product-card-bottom">
{info}
{action}
</div>
</div>
</ProductCardContext.Provider>
);
}
ProductCard.Image = ProductImage;
ProductCard.Button = ProductButton;
ProductCard.Title = ProductTitle;
ProductCard.Info = ProductInfo;
ProductCard.Category = ProductCategory;
ProductCard.Rating = ProductRating;
ProductCard.Price = ProductPrice;
export default ProductCard;
最後在使用 ProductCard的時候,就可以依自己的需求加入元件。再修復bug的時候也可以看哪裡出問題對症下藥
<ProductCard
product={product}
image={<ProductCard.Image />}
info={
<ProductCard.Info>
<ProductCard.Category />
<ProductCard.Title />
<ProductCard.Rating />
<ProductCard.Price />
</ProductCard.Info>
}
action={
<ProductCard.Button onClick={addToCart}>Add to cart</ProductCard.Button>
}
/>